
#region Copyright 
/*-------------------------------------------------------------------------
* 命名空间名称/文件名:    SSPivot.Common.Helpers.Http/HttpClientService 
* CLR版本:     4.0.30319.42000
* 机器名称:    DESKTOP-NHMLO8A
* 功 能：       N/A
* 类 名：       HttpClientService
* 创建时间：  2025/5/21 18:39:10
* 版本号：      v1.0
* 创建人:        xulong
*-------------------------------------------------------------------------*/
#endregion
using Microsoft.Extensions.Options;
using Polly;
using Polly.Extensions.Http;
using Polly.Timeout;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace SSPivot.Common.Helpers.Http
{
    /// <summary>
    /// 提供带重试和超时机制的HTTP客户端服务
    /// 支持配置驱动的HTTP请求处理，包括：
    /// - 自动重试策略
    /// - 超时控制
    /// - 统一错误处理
    /// - 请求/响应日志记录
    /// </summary>
    public class HttpClientService : IHttpClientService
    {
        private readonly IHttpClientFactory _clientFactory; // HttpClient工厂
        private readonly IOptionsMonitor<Dictionary<string, HttpClientConfig>> _configMonitor; // 配置监控
        private readonly JsonSerializerOptions _jsonOptions; // JSON序列化选项
        /// <summary>
        /// 默认useragent
        /// </summary>
        public static readonly string DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36";

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="clientFactory"></param>
        /// <param name="configMonitor"></param>
        /// <param name="jsonOptions"></param>
        public HttpClientService(
            IHttpClientFactory clientFactory,
            IOptionsMonitor<Dictionary<string, HttpClientConfig>> configMonitor,
            JsonSerializerOptions jsonOptions = null)
        {
            _clientFactory = clientFactory;
            _configMonitor = configMonitor;
            _jsonOptions = jsonOptions ?? new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true
            };
        }
        /// <summary>
        /// httpclient get
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="clientName"></param>
        /// <param name="url"></param>
        /// <param name="data"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public async Task<ServiceResult<T>> GetAsync<T>(string clientName, string url, Dictionary<string, string> data, RequestOptions? options = null)
        {
            return await ExecuteAsync<T>(clientName, async client =>
            {
                if (data != null && data.Count > 0) url = url + (url.EndsWith("?") ? "&" : "?") + GenerateParameterString(data);
                var request = CreateRequest(HttpMethod.Get, url, options);
                return await client.SendAsync(request);
            }, options);
        }
        /// <summary>
        /// httpclient post
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="clientName"></param>
        /// <param name="url"></param>
        /// <param name="data"></param>
        /// <param name="options"></param>
        /// <returns></returns>
        public async Task<ServiceResult<T>> PostAsync<T>(string clientName, string url, Dictionary<string, string> data, RequestOptions? options = null)
        {
            return await ExecuteAsync<T>(clientName, async client =>
            {
                var request = CreateRequest(HttpMethod.Post, url, options);
                request.Content = new FormUrlEncodedContent(data ?? new Dictionary<string, string>());
                return await client.SendAsync(request);
            }, options);
        }

        // PutAsync和DeleteAsync实现类似...

        /// <summary>
        /// 执行HTTP请求的核心方法
        /// </summary>
        /// <typeparam name="T">响应数据类型</typeparam>
        /// <param name="clientName">客户端配置名称</param>
        /// <param name="operation">HTTP操作委托</param>
        /// <param name="options">请求选项</param>
        /// <returns>封装的服务结果</returns>
        /// <remarks>
        /// 此方法会应用配置的重试和超时策略，并统一处理所有异常
        /// </remarks>
        private async Task<ServiceResult<T>> ExecuteAsync<T>(
            string clientName,
            Func<HttpClient, Task<HttpResponseMessage>> operation,
            RequestOptions options)
        {
            var result = new ServiceResult<T>();
            try
            {
                var client = GetConfiguredClient(clientName, options);
                var policy = CreatePolicy(clientName, options);

                var response = await policy.ExecuteAsync(async () =>
                    await operation(client));

                result.Code = response.StatusCode.ToString();
                result.Success = response.IsSuccessStatusCode;

                if (response.IsSuccessStatusCode)
                {
                    var content = await response.Content.ReadAsStringAsync();
                    result.Data = content.DeserializeJson<T>();
                }
                else
                {
                    result.Msg = await response.Content.ReadAsStringAsync();
                }
            }
            catch (Exception ex)
            {
                result.Success = false;
                result.Msg = ex.Message;
                result.Code = ex switch
                {
                    HttpRequestException httpEx => httpEx.StatusCode.ToString() ?? HttpStatusCode.InternalServerError.ToString(),
                    TaskCanceledException => HttpStatusCode.RequestTimeout.ToString(),
                    _ => HttpStatusCode.InternalServerError.ToString()
                };
                //Serilog.Log.Error($"----------\r\nDate:{DateTime.Now.ToString()}\r\nMsg:{ex.Message}\r\nTrace:{ex.StackTrace}");
            }
            return result;
        }

        /// <summary>
        /// 获取配置好的HttpClient实例
        /// </summary>
        /// <param name="clientName">客户端配置名称</param>
        /// <param name="options">请求选项</param>
        /// <returns>配置好的HttpClient实例</returns>
        private HttpClient GetConfiguredClient(string clientName, RequestOptions options)
        {
            var client = _clientFactory.CreateClient(clientName);

            if (options?.Timeout != null)
            {
                client.Timeout = options.Timeout.Value;
            }

            return client;
        }

        /// <summary>
        /// 创建Polly策略组合(重试+超时)
        /// </summary>
        /// <param name="clientName">客户端配置名称</param>
        /// <param name="options">请求选项</param>
        /// <returns>策略组合</returns>
        private IAsyncPolicy<HttpResponseMessage> CreatePolicy(string clientName, RequestOptions options)
        {
            var config = _configMonitor.Get(clientName);
            var retryCount = options?.RetryCount;
            var timeout = options?.Timeout;

            if (config.ContainsKey(clientName))
            {
                retryCount = options?.RetryCount ?? config[clientName].RetryCount;
                timeout = options?.Timeout ?? config[clientName].Timeout;
            }
            var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(
                (int)timeout?.TotalSeconds, TimeoutStrategy.Optimistic);


            var retryPolicy = HttpPolicyExtensions
                .HandleTransientHttpError()
                .OrResult(r => !r.IsSuccessStatusCode)
                .WaitAndRetryAsync(retryCount.Value, _ => TimeSpan.FromMilliseconds(500));


            return Policy.WrapAsync(retryPolicy, timeoutPolicy);
        }

        /// <summary>
        /// 创建配置好的HttpRequestMessage
        /// </summary>
        /// <param name="method">HTTP方法</param>
        /// <param name="url">请求URL</param>
        /// <param name="options">请求选项</param>
        /// <returns>配置好的请求消息</returns>
        /// <remarks>
        /// 会自动合并默认Header和自定义Header，处理Cookie和Referer等
        /// </remarks>
        private HttpRequestMessage CreateRequest(HttpMethod method, string url, RequestOptions? options)
        {
            var request = new HttpRequestMessage(method, url);

            // 合并默认Header和自定义Header
            var configHeaderName = "";
            request.Options.TryGetValue("clientName", out string name);
            if (name.IsNotNullOrWhiteSpace()) configHeaderName = name;
            var config = _configMonitor.Get(configHeaderName);
            foreach (var header in config[configHeaderName].DefaultHeaders)
            {
                request.Headers.TryAddWithoutValidation(header.Key, header.Value);
            }

            if (options?.Headers != null)
            {
                foreach (var header in options.Headers)
                {
                    request.Headers.TryAddWithoutValidation(header.Key, header.Value);
                }
            }

            if (options?.Cookies != null && options.Cookies.Count > 0)
            {
                request.Headers.Add("Cookie", BuildCookieHeader(options.Cookies));
            }

            if (!string.IsNullOrEmpty(options?.ReferUrl))
            {
                request.Headers.Referrer = new Uri(options.ReferUrl);
            }
            if (!request.Headers.Contains("content-type") && !request.Headers.Contains("Content-Type"))
            {
                request.Headers.TryAddWithoutValidation("Content-Type", "application/json");
            }
            if (!request.Headers.Contains("user-agent") && !request.Headers.Contains("User-Agent"))
            {
                request.Headers.TryAddWithoutValidation("User-Agent\"", DefaultUserAgent);
            }

            return request;
        }

        /// <summary>
        /// 构建Cookie请求头字符串
        /// </summary>
        /// <param name="cookies">Cookie字典</param>
        /// <returns>格式化的Cookie头字符串</returns>
        private string BuildCookieHeader(Dictionary<string, string> cookies)
        {
            return string.Join("; ", cookies.Select(kv => $"{kv.Key}={kv.Value}"));
        }
        /// <summary>
        /// 根据字典生成URL参数
        /// </summary>
        /// <param name="requestParams">请求参数</param>
        /// <returns>URL参数</returns>
        private static string GenerateParameterString(IDictionary<string, string> requestParams)
        {
            string paramstring = "";
            if (requestParams != null)
            {
                foreach (KeyValuePair<string, string> pair in requestParams)
                {
                    paramstring += pair.Key + "=" + pair.Value + "&";
                }
                paramstring = paramstring.Substring(0, paramstring.Length - 1);
            }
            return paramstring;
        }
    }

}
